#ifndef _SETTINGS_WEB_SERVER_H
#define _SETTINGS_WEB_SERVER_H


#define REST_CHECK(a, str, goto_tag, ...)                                              \
    do                                                                                 \
    {                                                                                  \
        if (!(a))                                                                      \
        {                                                                              \
            goto goto_tag;                                                             \
        }                                                                              \
    } while (0)

#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + 128)
#define SCRATCH_BUFSIZE (10240)

typedef struct restServerContext {
	char basePath[ESP_VFS_PATH_MAX + 1];
	char scratch[SCRATCH_BUFSIZE];
} restServerContext_t;

#define CHECK_FILE_EXTENSION(filename, ext) (strcasecmp(&filename[strlen(filename) - strlen(ext)], ext) == 0)




/* Set HTTP response content type according to file extension */
static esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filepath) {
	const char *type = "text/plain";
	if (CHECK_FILE_EXTENSION(filepath, ".html")) {
		type = "text/html";
	} else if (CHECK_FILE_EXTENSION(filepath, ".js")) {
		type = "application/javascript";
	} else if (CHECK_FILE_EXTENSION(filepath, ".css")) {
		type = "text/css";
	} else if (CHECK_FILE_EXTENSION(filepath, ".png")) {
		type = "image/png";
	} else if (CHECK_FILE_EXTENSION(filepath, ".ico")) {
		type = "image/x-icon";
	} else if (CHECK_FILE_EXTENSION(filepath, ".svg")) {
		type = "text/xml";
	}
	return httpd_resp_set_type(req, type);
}


static esp_err_t restCommonGetHandler(httpd_req_t *req) {
	char filepath[FILE_PATH_MAX];

	restServerContext_t *restContext = (restServerContext_t *)req->user_ctx;
	strlcpy(filepath, restContext->basePath, sizeof(filepath));
	if (req->uri[strlen(req->uri) - 1] == '/') {
		strlcat(filepath, "/index.html", sizeof(filepath));
	} else {
		strlcat(filepath, req->uri, sizeof(filepath));
	}
	int fd = open(filepath, O_RDONLY, 0);
	if (fd == -1) {
		httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to read existing file");
		return ESP_FAIL;
	}

	set_content_type_from_file(req, filepath);

	char *chunk = restContext->scratch;
	ssize_t read_bytes;
	do {
		read_bytes = read(fd, chunk, SCRATCH_BUFSIZE);
		if (read_bytes == -1) {
		} else if (read_bytes > 0) {
			if (httpd_resp_send_chunk(req, chunk, read_bytes) != ESP_OK) {
				close(fd);

				httpd_resp_sendstr_chunk(req, NULL);
				httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file");
				return ESP_FAIL;
			}
		}
	} while (read_bytes > 0);

	close(fd);

	httpd_resp_send_chunk(req, NULL, 0);
	// showSettings();
	return ESP_OK;
}


static esp_err_t setSettingsDataHandler(httpd_req_t *req) {
	char *content = (char *)malloc(req->content_len + 1);
    int ret = httpd_req_recv(req, content, req->content_len);
	content[req->content_len] = '\0';
	
	if (ret <= 0) {
		if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
			httpd_resp_send_408(req);
		}
		return ESP_FAIL;
	}

	cJSON *st	= NULL;
	st = cJSON_Parse(content);
	if(st == NULL) {
		httpd_resp_sendstr(req, "读取数据失败");
		free(content);
		return ESP_OK;
    }

	cJSON *item	= NULL;
	for(int i = 0; i < ST_CNT; i ++) {
		item = cJSON_GetObjectItem(st, stGroup[i].itemName);
		if(strcmp(stGroup[i].valueType, "str") == 0) {
			strcpy(stGroup[i].strValue, item->valuestring);
		}
		else if (strcmp(stGroup[i].valueType, "int") == 0) {
			stGroup[i].intValue = atoi(item->valuestring);
		}
	}
	saveSettings();
	httpd_resp_sendstr(req, "完成");
	free(content);
	return ESP_OK;
}


static esp_err_t resetSettingsHandler(httpd_req_t *req) {
	for(int i = 0; i < ST_CNT; i ++) {
		if(strcmp(stGroup[i].itemName, "ssid") != 0 && strcmp(stGroup[i].itemName, "password") != 0) {
			if(strcmp(stGroup[i].valueType, "str") == 0) {
				strcpy(stGroup[i].strValue, stGroup[i].defaultStrValue);
			}
			else if(strcmp(stGroup[i].valueType, "int") == 0) {
				stGroup[i].intValue = stGroup[i].defaultIntValue;
			}
		}
	}
	saveSettings();
	httpd_resp_sendstr(req, "完成");
	return ESP_OK;
}

static esp_err_t eraseNvsHandler(httpd_req_t *req) {
	nvs_flash_erase();
	httpd_resp_sendstr(req, "完成");
	return ESP_OK;
}

static esp_err_t getSettingsDataHandler(httpd_req_t *req) {
	char *content[] = {0};
    int ret = httpd_req_recv(req, (char *)content, req->content_len);

	if (ret <= 0) {  /* 返回 0 表示连接已关闭 */
		/* 检查是否超时 */
		if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
			httpd_resp_send_408(req);
		}
		return ESP_FAIL;
	}
	loadSettings();
	cJSON *root = cJSON_CreateObject();

	for(int i = 0; i < ST_CNT; i++) {
		if(strcmp(stGroup[i].valueType, "str") == 0) {
			cJSON_AddStringToObject(root, stGroup[i].itemName, stGroup[i].strValue);
		}
		else if(strcmp(stGroup[i].valueType, "int") == 0) {
			cJSON_AddNumberToObject(root, stGroup[i].itemName, stGroup[i].intValue);
		}
	}

	httpd_resp_set_type(req, "application/json");
	const char *settings = cJSON_Print(root);
	httpd_resp_sendstr(req, settings);
	free((void *)settings);
	cJSON_Delete(root);
	// httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
	return ESP_OK;
}


esp_err_t startRestServer(const char *basePath) {
	// dispInitInfo("Init WebServer...");
	REST_CHECK(basePath, "wrong base path", err);
	restServerContext_t *restContext = calloc(1, sizeof(restServerContext_t));
	REST_CHECK(restContext, "No memory for rest context", err);
	strlcpy(restContext->basePath, basePath, sizeof(restContext->basePath));

	httpd_handle_t server = NULL;
	httpd_config_t config = HTTPD_DEFAULT_CONFIG();
	config.uri_match_fn = httpd_uri_match_wildcard;

	REST_CHECK(httpd_start(&server, &config) == ESP_OK, "Start server failed", err_start);

	httpd_uri_t common_get_uri = {
		.uri = "/*",
		.method = HTTP_GET,
		.handler = restCommonGetHandler,
		.user_ctx = restContext
	};
	httpd_register_uri_handler(server, &common_get_uri);


	httpd_uri_t getSettingsDataUri = {
		.uri = "/api/v1/get_settings_data",
		.method = HTTP_POST,
		.handler = getSettingsDataHandler,
		.user_ctx = restContext
	};
	httpd_register_uri_handler(server, &getSettingsDataUri);

	httpd_uri_t setSettingsDataUri = {
		.uri = "/api/v1/set_settings_data",
		.method = HTTP_POST,
		.handler = setSettingsDataHandler,
		.user_ctx = restContext
	};
	httpd_register_uri_handler(server, &setSettingsDataUri);

	httpd_uri_t resetSettingsUri = {
		.uri = "/api/v1/reset_settings",
		.method = HTTP_POST,
		.handler = resetSettingsHandler,
		.user_ctx = restContext
	};
	httpd_register_uri_handler(server, &resetSettingsUri);

	httpd_uri_t eraseNvsUri = {
		.uri = "/api/v1/erase_nvs",
		.method = HTTP_POST,
		.handler = eraseNvsHandler,
		.user_ctx = restContext
	};
	httpd_register_uri_handler(server, &eraseNvsUri);

	dispInitInfo("WebServer init done.");
	return ESP_OK;
err_start:
	free(restContext);
err:
	dispInitInfo("WebServer init failed.");
	return ESP_FAIL;
}




#endif